#include "BenchmarkWidget.hpp"
#include "../cl/Benchmark.hpp"
#include <QtConcurrent/QtConcurrent>
#include <QVBoxLayout>
#include <QGridLayout>
#include <QTextEdit>
#include <QLabel>
#include <QMessageBox>

using namespace ui;

namespace {
    const int COLUMNS = 4;//5;
}

BenchmarkWidget::BenchmarkWidget(const cl::Device &device, QWidget *parent) :
    QWidget(parent),
    textEdit(new QTextEdit()),
    benchmark(new cl::Benchmark(device)),
    hasDoubleOption(false) {
    setLayout(new QVBoxLayout());
    QGridLayout *gridLayout = new QGridLayout();
    gridLayout->setMargin(0);
    QWidget *displayWidget = new QWidget();
    displayWidget->setLayout(gridLayout);
    QLabel *fp32    = new QLabel("float");
    QLabel *fp32_2  = new QLabel("float2");
    QLabel *fp32_4  = new QLabel("float4");
    QLabel *fp32_8  = new QLabel("float8");
    //QLabel *fp32_16 = new QLabel("float16");
    QLabel *spacer1 = new QLabel();
    QLabel *fp64    = new QLabel("double");
    QLabel *fp64_2  = new QLabel("double2");
    QLabel *fp64_4  = new QLabel("double4");
    QLabel *fp64_8  = new QLabel("double8");
    //QLabel *fp64_16 = new QLabel("double16");
    QLabel *spacer2 = new QLabel();
    gridLayout->addWidget(fp32,    0, 0);
    gridLayout->addWidget(fp32_2,  0, 1);
    gridLayout->addWidget(fp32_4,  0, 2);
    gridLayout->addWidget(fp32_8,  0, 3);
    //gridLayout->addWidget(fp32_16, 0, 4);
    gridLayout->addWidget(spacer1, 2, 0);
    gridLayout->addWidget(fp64,    3, 0);
    gridLayout->addWidget(fp64_2,  3, 1);
    gridLayout->addWidget(fp64_4,  3, 2);
    gridLayout->addWidget(fp64_8,  3, 3);
    //gridLayout->addWidget(fp64_16, 3, 4);
    gridLayout->addWidget(spacer2, 5, 0);
    for(int i=0; i<COLUMNS; i++) {
        QLabel *label_fp32 = new QLabel("0.00 GFlop/s");
        QLabel *label_fp64 = new QLabel("0.00 GFlop/s");
        label_fp32->setStyleSheet("border: 1px solid grey");
        label_fp64->setStyleSheet("border: 1px solid grey");
        gridLayout->addWidget(label_fp32, 1, i);
        gridLayout->addWidget(label_fp64, 4, i);
        floatValues.append(label_fp32);
        doubleValues.append(label_fp64);
    }
    layout()->addWidget(displayWidget);
    layout()->addWidget(textEdit);
    textEdit->setReadOnly(true);
    connect(benchmark, SIGNAL(contextError(const char*)),
            this,      SLOT(onContextError(const char*)));
}

BenchmarkWidget::~BenchmarkWidget(void) {
    futureBenchmarkWatcher.waitForFinished();
    if(benchmark) { delete benchmark; benchmark = 0; }
}

void BenchmarkWidget::buildProgram(void) {
    emit isRunning(true);
    textEdit->append("Building Benchmark Program");
    connect(benchmark, SIGNAL(programBuilt(int,const char*,const char*)),
            this,      SLOT(onProgramBuilt(int,const char*,const char*)));
    benchmark->loadProgram();
}

void BenchmarkWidget::onProgramBuilt(
        int status, const char *options, const char *log) {
    textEdit->append(QString("OpenCL C Build '")
                     .append(hasDoubleOption ?
                                 "benchmark_fp64.cl" : "benchmark_fp32.cl")
                     .append(" ").append(options).append("'"));
    textEdit->append(
                (status == cl::Benchmark::BUILD_SUCCESS)     ? "BUILD SUCCESS" :
                (status == cl::Benchmark::BUILD_NONE)        ? "BUILD NONE" :
                (status == cl::Benchmark::BUILD_IN_PROGRESS) ? "BUILD IN PROGRESS" :
                (status == cl::Benchmark::BUILD_ERROR)       ? "BUILD ERROR" : "");
    textEdit->append(log);
    bool hasDouble = benchmark->hasDouble();
    if(hasDouble && !hasDoubleOption) {
        hasDoubleOption = true;
        benchmark->loadProgram(hasDouble);
    } else {
        textEdit->append("Running Benchmark");
        // Async call.
        futureBenchmark = QtConcurrent::run(this, &BenchmarkWidget::runBenchmark);
        connect(&futureBenchmarkWatcher, SIGNAL(finished()),
                this, SLOT(onBenchmarkDone()));
        futureBenchmarkWatcher.setFuture(futureBenchmark);
    }
}

void BenchmarkWidget::onBenchmarkDone(void) {
    textEdit->append("Benchmark Done");
    emit isRunning(false);
}

void BenchmarkWidget::onContextError(const char *errorInfo) {
    QMessageBox::critical(this, "clMark", errorInfo,
                          QMessageBox::Close, QMessageBox::NoButton);
}

void BenchmarkWidget::runBenchmark(void) {
    runFloatBenchmark();
    runDoubleBenchmark();
}

void BenchmarkWidget::runFloatBenchmark(void) {
    double flops = 0.0;
    benchmark->runTest(cl::Benchmark::KERNEL_FP32, cl::Benchmark::N1);
    flops = benchmark->getProfilingPerformance();
    floatValues[0]->setText(formatGFlops(flops));
    benchmark->runTest(cl::Benchmark::KERNEL_FP32, cl::Benchmark::N2);
    flops = benchmark->getProfilingPerformance();
    floatValues[1]->setText(formatGFlops(flops));
    benchmark->runTest(cl::Benchmark::KERNEL_FP32, cl::Benchmark::N4);
    flops = benchmark->getProfilingPerformance();
    floatValues[2]->setText(formatGFlops(flops));
    benchmark->runTest(cl::Benchmark::KERNEL_FP32, cl::Benchmark::N8);
    flops = benchmark->getProfilingPerformance();
    floatValues[3]->setText(formatGFlops(flops));
    /*benchmark->runKernel(cl::Benchmark::KERNEL_FP32, items, cl::Benchmark::N16);
    flops = benchmark->getProfilingPerformance();
    floatValues[4]->setText(formatGFlops(flops));*/
}

void BenchmarkWidget::runDoubleBenchmark(void) {
    if(!benchmark->hasDouble()) {
        return;
    }
    double flops = 0.0;
    benchmark->runTest(cl::Benchmark::KERNEL_FP64, cl::Benchmark::N1);
    flops = benchmark->getProfilingPerformance();
    doubleValues[0]->setText(formatGFlops(flops));
    benchmark->runTest(cl::Benchmark::KERNEL_FP64, cl::Benchmark::N2);
    flops = benchmark->getProfilingPerformance();
    doubleValues[1]->setText(formatGFlops(flops));
    benchmark->runTest(cl::Benchmark::KERNEL_FP64, cl::Benchmark::N4);
    flops = benchmark->getProfilingPerformance();
    doubleValues[2]->setText(formatGFlops(flops));
    benchmark->runTest(cl::Benchmark::KERNEL_FP64, cl::Benchmark::N8);
    flops = benchmark->getProfilingPerformance();
    doubleValues[3]->setText(formatGFlops(flops));
    /*benchmark->runKernel(cl::Benchmark::KERNEL_FP64, cl::Benchmark::N16);
    flops = benchmark->getProfilingPerformance();
    doubleValues[4]->setText(formatGFlops(flops));*/
}

QString BenchmarkWidget::formatGFlops(double value) {
    double GFlops = value / 1.0e+9;
    return QString::number(GFlops, 'g', 3).append(" GFlop/s");
}
